/*
  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
  Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)

  Stockfish is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  Stockfish is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "timeman.h"

#include <algorithm>
#include <cmath>

#include "search.h"

namespace Stockfish {

#define DEFAULT_MTG 120
extern TimePoint maxMovetime;

TimePoint TimeManagement::optimum() const { return optimumTime; }
TimePoint TimeManagement::maximum() const { return maximumTime; }

// Called at the beginning of the search and calculates
// the bounds of time allowed for the current game ply. We currently support:
//      1) x basetime (+ z increment)
//      2) x moves in y seconds (+ z increment)
void TimeManagement::init(Search::LimitsType& limits,
                          Color               us,
                          int                 ply,
                          double&             originalTimeAdjust) {

    // If we have no time, we don't need to fully initialize TM.
    // startTime is used by movetime.
    startTime    = limits.startTime;

    if (maxMovetime) {
        if (limits.movetime) {
            limits.movetime = std::min(limits.movetime, maxMovetime);
        } else {
            limits.movetime = maxMovetime;
        }
    }

    if (limits.time[us] == 0) {
        limits.time[~us] = 0;
        if (limits.byotime) {
            if (limits.movetime) {
                limits.movetime = std::min(limits.movetime, limits.byotime);
            } else {
                limits.movetime = limits.byotime;
            }
        }
        return;
    } else if (limits.byotime) {
        if (limits.movetime) {
            limits.movetime = std::min(limits.time[us] + limits.byotime, limits.movetime);
        } else {
            limits.movetime = limits.time[us] + limits.byotime;
        }
        limits.time[~us] = (limits.time[us] += limits.byotime * DEFAULT_MTG);
    }

    // optScale is a percentage of available time to use for the current move.
    // maxScale is a multiplier applied to optimumTime.
    double optScale, maxScale;

    // These numbers are used where multiplications, divisions or comparisons
    // with constants are involved.
    const TimePoint scaledTime  = limits.time[us];
    const TimePoint scaledInc   = limits.inc[us];

    // Maximum move horizon
    int centiMTG = limits.movestogo ? std::min(limits.movestogo, DEFAULT_MTG) : DEFAULT_MTG;

    // If less than one second, gradually reduce mtg
    if (scaledTime < 1000 && double(centiMTG) / scaledInc > 0.1)
    {
        centiMTG = scaledTime * 0.1;
    }

    // Make sure timeLeft is > 0 since we may use it as a divisor
    TimePoint timeLeft = std::max(
        TimePoint(1),
        limits.time[us] + (limits.inc[us] * (centiMTG - 1) * 4 / 5)
    );

    // x basetime (+ z increment)
    // If there is a healthy increment, timeLeft can exceed the actual available
    // game time for the current move, so also cap to a percentage of available game time.
    if (limits.movestogo == 0)
    {
        // Extra time according to timeLeft
        if (originalTimeAdjust < 0)
            originalTimeAdjust = 0.3285 * std::log10(timeLeft) - 0.4830;

        // Calculate time constants based on current time left.
        double logTimeInSec = std::log10(scaledTime / 1000.0);
        double optConstant  = std::min(0.00344000 + 0.000200000 * logTimeInSec, 0.00450000);
        double maxConstant  = std::max(3.9000 + 3.10000 * logTimeInSec, 2.50000);

        optScale = std::min(0.0155000 + std::pow(ply + 3.00000, 0.450000) * optConstant,
                            0.200000 * limits.time[us] / timeLeft)
                 * originalTimeAdjust;

        maxScale = std::min(6.50000, maxConstant + ply / 13.6000);
    }

    // x moves in y seconds (+ z increment)
    else
    {
        optScale =
          std::min((0.88 + ply / 116.4) / double(centiMTG), 0.88 * limits.time[us] / timeLeft);
        maxScale = 1.3 + 0.11 * double(centiMTG);
    }

    // Limit the maximum possible time for this move
    optimumTime = TimePoint(optScale * timeLeft) * 4 / 5;
    maximumTime =
      TimePoint(std::min(0.810000 * limits.time[us], maxScale * optimumTime)) * 4 / 5;
}

}  // namespace Stockfish
